# +----------------------------------------------------------------------
# | ChatWork智能聊天办公系统
# +----------------------------------------------------------------------
# | 软件声明: 本系统并非自由软件,未经授权任何形式的商业使用均属非法。
# | 版权保护: 任何企业和个人不允许对程序代码以任何形式任何目的复制/分发。
# | 授权要求: 如有商业使用需求,请务必先与版权所有者取得联系并获得正式授权。
# +----------------------------------------------------------------------
# | Author: ChatWork Team <2474369941@qq.com>
# +----------------------------------------------------------------------
import json
import re
import time
from typing import Dict
from tortoise.transactions import in_transaction
from exception import AppException
from common.enums.wallet import WalletEnum
from common.utils.tools import ToolsUtil
from common.utils.config import ConfigUtil
from common.models.users import UserModel
from common.models.users import UserAuthModel
from common.models.users import UserWalletModel
from common.models.users import UserSubscribeModel
from common.models.market import SubscribePackageModel
from common.models.market import SubscribePricingModel
from plugins.safe.driver import SecurityDriver


class UserWidget:
    # 禁用账号
    FORBID_ACCOUNT = (
        # 敏感账号
        "admin", "root", "superuser", "sysadmin", "super", "manage",
        "support", "helpdesk", "customer", "feedback", "administrators",
        "apple", "google", "microsoft", "elonmusk", "billgates", "administrate",
        "test", "demo", "guest", "user123", "temp", "officer", "governance",
        "jenkins", "gitlab", "jira", "confluence", "supervise", "govern",
        "service", "webhook", "auth", "init", "golang", "organize", "gratis",
        "mysql", "postgres", "oracle", "sqlserver", "control", "free",
        "beijing", "shanghai", "alibaba", "tencent", "task", "direction",
        "reboot", "shutdown", "delete", "reset", "make", "have", "author",
        "install", "mobile", "email", "fuck", "shit", "whore", "writer", "read"
        "cookie", "prick", "good", "that", "www",  "handling",

        # 易猜账号
        "1111", "1234", "12345", "123456", "111111", "000000",
        "2222", "1122", "3333", "4444", "aaaa", "bbb", "1231",
        "qwer", "asdf", "zxcv", "john", "mary", "mike", "guest",
        "19900101", "05202023", "1996", "2050", "1920", "12580"
    )

    @classmethod
    async def create_user(cls, response: Dict[str, str]) -> int:
        """
        创建用户

        Args:
            response (Dict[str, str]): 用户资料

        Returns:
            int: 用户ID

        Author:
            zero
        """
        # 接收参数
        sn_code: str = await ToolsUtil.make_rand_sn(UserModel, "sn", 8)
        terminal: int = int(response.get("terminal", 0))
        avatar: str = response.get("avatarUrl", "static/images/default_avatar.png")
        account: str = response.get("account", "u" + sn_code)
        nickname: str = response.get("nickname", sn_code)
        password: str = response.get("password", "")
        email: str = response.get("email", "")
        mobile: str = response.get("mobile", "")
        openid: str = response.get("openid", "")
        unionid: str = response.get("unionid", "")
        gender: int = int(response.get("gender", 0))

        # 验证账号
        if account:
            account_pattern = r'^[a-zA-Z0-9]+$'
            if not re.fullmatch(account_pattern, account):
                raise AppException("登陆账号只能是 英文字母 或 数字")
            if account in cls.FORBID_ACCOUNT:
                raise AppException("抱歉，该账号不可用: " + account)

        # 验证密码: [字母+数字+特殊符号]
        if password:
            pwd_pattern = r'^(?=.*[a-z])(?=.*\d)[a-zA-Z\d!@#$%^&*(),.?":{}|<>.]{6,20}$'
            if not re.fullmatch(pwd_pattern, password):
                raise AppException("密码必须要包含 英文字母、数字、特殊符号")

        # 验证邮箱
        if email:
            email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
            if not re.fullmatch(email_pattern, email):
                raise AppException("邮箱格式不正确")

        # 验证账号
        if account and await UserModel.filter(account=account, is_delete=0).first():
            raise AppException("账号已被占用")

        # 验证手机
        if mobile and await UserModel.filter(mobile=mobile, is_delete=0).first():
            raise AppException("手机已被占用")

        # 验证邮箱
        if email and await UserModel.filter(email=email, is_delete=0).first():
            raise AppException("邮箱已被占用")

        # 密码信息
        salt: str = ToolsUtil.make_rand_char(6)
        if password:
            password = ToolsUtil.make_md5_str(password, salt)

        try:
            async with in_transaction("mysql"):
                # 创建用户
                user = await UserModel.create(
                    sn=sn_code,
                    avatar=avatar,
                    mobile=mobile,
                    email=email,
                    account=account,
                    password=password,
                    nickname=nickname,
                    gender=gender,
                    salt=salt,
                    last_login_ip="",
                    last_login_time=int(time.time()),
                    create_time=int(time.time()),
                    update_time=int(time.time())
                )

                # 创建授权
                if openid or unionid:
                    await UserAuthModel.create(
                        user_id=user.id,
                        openid=openid,
                        unionid=unionid,
                        terminal=terminal,
                        create_time=int(time.time()),
                        update_time=int(time.time())
                    )

                # 注册赠送
                await cls.register_reward(user_id=user.id)

                return user.id
        except Exception as e:
            raise AppException(str(e))

    @classmethod
    async def update_user(cls, response: Dict[str, str]) -> int:
        """
        更新用户

        Args:
            response (Dict[str, str]): 用户资料

        Returns:
            int: 用户ID

        Author:
            zero
        """
        # 接收参数
        user_id: int = int(response.get("user_id"))
        terminal: int = int(response.get("terminal"))
        mobile: str = response.get("mobile", "")
        openid: str = response.get("openid", "")
        unionid: str = response.get("unionid", "")

        # 查询用户
        user_info = await UserModel.filter(id=user_id, is_delete=0).first()
        user_auth = await UserAuthModel.filter(user_id=user_id, terminal=terminal).first()

        # 验证手机
        if mobile and UserModel.filter(mobile=mobile, is_delete=0, id__ne=user_id).first():
            raise AppException("手机已被占用")

        try:
            async with in_transaction("mysql"):
                # 绑定手机
                if mobile and not user_info.mobile:
                    await UserModel.filter(id=user_id).update(mobile=mobile, update_time=int(time.time()))

                # 创建授权
                if not user_auth and (openid or unionid):
                    await UserAuthModel.create(
                        user_id=user_id,
                        openid=openid,
                        unionid=unionid,
                        terminal=terminal,
                        create_time=int(time.time()),
                        update_time=int(time.time())
                    )

                # 更新关联
                if (user_auth and not user_auth.unionid) and unionid:
                    await UserAuthModel.filter(id=user_auth.id).update(
                        unionid=unionid,
                        update_time=int(time.time())
                    )

                return user_id
        except Exception as e:
            raise AppException(str(e))

    @classmethod
    async def openid_user(cls, openid: str):
        """
        根据openid获取用户信息

        Args:
            openid (str): 微信的openid

        Returns:
            User: 用户信息

        Author:
            zero
        """
        auth = (await UserAuthModel
                .filter(openid=openid)
                .order_by("-id")
                .first()
                .values("id", "user_id") or {})

        user = (await UserModel
                .filter(id=auth.get("user_id", 0), is_delete=0)
                .order_by("-id")
                .first())

        if auth and not user:
            await UserAuthModel.delete(auth["id"])

        return user

    @classmethod
    async def gran_token(cls, user_id: int, terminal: int) -> str:
        """
        生成登录令牌

        Args:
            user_id (int): 用户ID
            terminal (int): 来源客户端

        Returns:
            str: token

        Author:
            zero
        """
        return await SecurityDriver.module("api").login(user_id, terminal)

    @classmethod
    async def register_reward(cls, user_id: int):
        """
        注册赠送

        Args:
            user_id (int): 用户ID

        Author:
            zero
        """
        user = await UserModel.filter(id=user_id).first()
        if not user:
            return

        # 默认订阅会员
        package = await SubscribePackageModel.filter(id=1).first()
        pricing = await SubscribePricingModel.filter(id=1).first()
        await UserSubscribeModel.create(
            user_id=user_id,
            package_id=package.id,
            pricing_id=pricing.id,
            is_perpetual=1,
            create_time=int(time.time()),
            update_time=int(time.time()),
            snapshot=json.dumps({
                "name": package.name,
                "description": package.description,
                "give_points": pricing.give_points,
                "train_num": package.train_num,
                "robot_num": package.robot_num,
                "know_num": package.know_num,
                "sell_price": str(pricing.sell_price),
                "line_price": str(pricing.line_price)
            }, ensure_ascii=False)
        )

        # 订阅套餐赠送
        if pricing.give_points > 0:
            user.points += pricing.give_points
            await user.save()
            await UserWalletModel.inc(
                scene=WalletEnum.POINTS,
                user_id=user_id,
                change_type=WalletEnum.POINTS_DEC_SUBSCRIBE,
                change_amount=pricing.give_points
            )

        # 注册赠送
        reward_flows = []
        reward = await ConfigUtil.get("register_reward") or {}
        if reward.get("status"):
            if reward.get("points"):
                s = WalletEnum.POINTS
                t = WalletEnum.POINTS_INC_REG_REWARD
                reward_flows.append({"scene": s, "type": t, "value": int(reward["points"])})
                user.points = user.points + int(reward["points"])
            if reward.get("train"):
                s = WalletEnum.TRAIN
                t = WalletEnum.TRAIN_INC_REG_REWARD
                reward_flows.append({"scene": s, "type": t, "value": int(reward["train"])})
                user.train_num = user.train_num + int(reward["train"])
            if reward.get("robot"):
                s = WalletEnum.ROBOT
                t = WalletEnum.ROBOT_INC_REG_REWARD
                reward_flows.append({"scene": s, "type": t, "value": int(reward["robot"])})
                user.robot_num = user.robot_num + int(reward["robot"])
            if reward.get("know"):
                s = WalletEnum.KNOW
                t = WalletEnum.KNOW_INC_REG_REWARD
                reward_flows.append({"scene": s, "type": t, "value": int(reward["know"])})
                user.know_num = user.know_num + int(reward["know"])
            if reward.get("mcp"):
                s = WalletEnum.MCP
                t = WalletEnum.MCP_INC_REG_REWARD
                reward_flows.append({"scene": s, "type": t, "value": int(reward["mcp"])})
                user.mcp_num = user.know_num + int(reward["mcp"])

            await user.save()
            for item in reward_flows:
                await UserWalletModel.inc(
                    scene=item["scene"],
                    user_id=user_id,
                    change_type=item["type"],
                    change_amount=item["value"]
                )
